<template>
<div :class="[$style.container]">
  <div :class="[$style.header]">
    <doc-nav :data="pageData" :current="current" :left-expand.sync="leftExpand" :right-expand.sync="rightExpand"></doc-nav>
  </div>
  <div :class="[$style.body]">
    <div :class="[$style.menu]" v-if="leftExpand">
      menu
    </div>
    <div :class="[$style.content]" ref="content" tabindex="0" @dragover="onDragover" @mouseup="onMouseUp">
      <div :class="[$style.contentInner]" @contextmenu.self="e => onContextmenu(e)">
        <doc-list :data="pageData" :selected-row.sync="selectedRow" :list-type="listType"></doc-list>
        <context-menu ref="contextmenu">
          <template v-slot="{}">
            <context-menu-item @click.native="createDoc({ type: 'dir', label: '文件夹' })" v-if="hasPerm('create', current)">新建文件夹</context-menu-item>
            <context-menu-item @click.native="uploadDoc({ multiple: true })"  v-if="hasPerm('upload', current)">上传文件</context-menu-item>
            <context-menu-item @click.native="uploadDoc({ directory: true, webkitdirectory: true })"  v-if="hasPerm('upload', current)">上传文件夹</context-menu-item>
            <context-menu-item @click.native="pageData.reload()">
              <span><i class="el-icon-refresh-left"></i></span> <span>刷新</span>
            </context-menu-item>
          </template>
        </context-menu>
        <empty-data v-if="!pageData.loading && pageData.rows.length < 1" @contextmenu.native.self="e => onContextmenu(e)">
          <div class="mb-3">还没添加任何文档，请添加文档，可直接从本地拖拽上传</div>
          <div v-if="hasPerm('upload', current)"><el-button type="primary" size="small" @click="uploadDoc({ multiple: true })">上传文件</el-button></div>
          <div class="mt-3"  v-if="hasPerm('upload', current)"><el-button size="small" type="text" @click="createDoc({ type: 'dir', label: '文件夹' })">新建文件夹</el-button></div>
        </empty-data>
        <div :class="[$style.uploadZone]" ref="uploadZone">
          <!-- directory webkitdirectory 需要处理文件夹 -->
          <input type="file" multiple :class="[$style.inputFile]" ref="inputFile"
            @dragleave="onDragLeave" @drop="onDrop" @change="onInputFileChange">
        </div>
      </div>
    </div>
    <div :class="[$style.aside]" v-if="rightExpand">
      <doc-panel :data="selectedRow"></doc-panel>
    </div>
  </div>
  <doc-new-modal ref="docNewModal"></doc-new-modal>
  <doc-lock-modal ref="docLockModal"></doc-lock-modal>
  <doc-rename-modal ref="docRenameModal"></doc-rename-modal>
  <doc-publish-modal ref="docPublishModal"></doc-publish-modal>
  <doc-add-tag-modal ref="docAddTagModal"></doc-add-tag-modal>
  <doc-mv-modal ref="docMvModal"></doc-mv-modal>
  <oss-upload ref="ossUpload"></oss-upload>
  <doc-link-edit-modal ref="docLinkEditModal"></doc-link-edit-modal>
  <doc-authorize-modal ref="docAuthorizeModal"></doc-authorize-modal>
  <doc-flow-modal ref="docFlowModal"></doc-flow-modal>
</div>
</template>

<script lang="ts">

import { Vue, Component, Provide, Watch, Ref, Prop } from 'vue-property-decorator'
import { getUserRootDocs, getUserDocs, getDocById, getUserRootDoc, del, getEntRootDocs, getEntRootDoc, updateTags, mv, download, unlock, cp, delInBatch, downloadWithPackage, existsName } from '@/api/doc'
import { PageData, PageQueryParams } from '@/config/page'
import { downloadFromBlob, downloadFromUrl, downloadTo, genTmpPath } from '@/utils/download'
import { AppModule } from '@/store'
import { DocNewModal, DocLockModal, DocRenameModal, DocPublishModal, DocAddTagModal, DocMvModal, DocNav, DocList, DocFlowModal } from './components'
import DocLinkEditModal from '@/views/pages/components/doc-link-edit-modal/index.vue'
import DocAuthorizeModal from '@/views/pages/docs/components/doc-authorize-modal/index.vue'
import DocPanel from '@/views/pages/components/doc-panel/index.vue'
import { hasPerm, PermissionName } from '@/config/acl'
import { canPreview } from '@/config/content-type'
import { mixins } from 'vue-class-component'
import FileUploadMixins from '@/mixins/file-upload-mixins'
import { openWindow } from '@/utils/url'
import { remote } from 'electron'
import openEx from 'open'
import { uploadFromLocal } from './upload'
import { genSha } from '@/utils/encrypt'
import { putInCache, removeFromCache, watchFile } from './watch'

@Component({
  components: { DocNewModal, DocLockModal, DocRenameModal, DocPublishModal, DocAddTagModal, DocMvModal, DocLinkEditModal, DocNav, DocList, DocPanel, DocAuthorizeModal, DocFlowModal }
})
export default class Docs extends mixins(FileUploadMixins) {
  @Prop() id!: number

  @Prop() entId!: number

  @Prop() selectedId!: number

  @Ref() docNewModal!: any

  @Ref() docLockModal!: any

  @Ref() docRenameModal!: any

  @Ref() docPublishModal!: any

  @Ref() docAddTagModal!: any

  @Ref() docMvModal!: any

  @Ref() docLinkEditModal!: DocLinkEditModal

  @Ref() docAuthorizeModal!: any

  @Ref() docFlowModal!: any

  @Ref() contextmenu!: any

  @Ref() ossUpload!: any

  @Ref() uploadZone!: HTMLDivElement

  @Ref() inputFile!: HTMLInputElement

  @Ref() content!: HTMLElement

  @AppModule.State currentSpace!: any

  @AppModule.State spaces!: any[]

  @AppModule.Mutation setCurrentSpace!: (space: any) => void

  leftExpand = false

  localRightExpand = this.$storage.get('doc-panel') !== false

  pageData: PageData<any> = null as any

  current: any = null

  selectedRow: any = null

  cpMvPasting = false

  listType: any = this.$storage.get('doc_query_list') || 'list'

  get rightExpand () {
    return !!this.localRightExpand
  }

  set rightExpand (expand: boolean) {
    this.localRightExpand = !!expand
    this.$storage.set('doc-panel', !!expand)
  }

  get checkedRows () {
    return this.pageData.rows.filter(v => v.checked)
  }

  hasPerm (name: PermissionName, ...rows: any[]) {
    return hasPerm(name, ...rows)
  }

  onContextmenu (e: MouseEvent) {
    this.contextmenu.open(e)
  }

  @Provide() getPageData () {
    return this.pageData
  }

  @Provide() createDoc (params: { type: string; label: string }) {
    const id = this.current?.id
    this.docNewModal.open({ ...params, id }).then((data: any) => {
      this.$message.success(`新建${params.label}成功`)
      this.pageData.reload()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() uploadDoc (params: any) {
    this.ossUpload.open(params, this.current)
  }

  @Provide() openDoc (row: any) {
    if (row?.type === 'dir') {
      this.$router.push({ path: this.$route.path, query: { id: row.id } })
    } else {
      if (!canPreview(row.name)) {
        this.$message.info('不支持预览该文件格式')
        return
      }
      if (!hasPerm('preview', row)) {
        this.$message.info('没有预览权限')
        return
      }
      // window.open(this.$router.resolve(`/preview/${row.id}?parentId=${row.parentId}`).href)
      openWindow(`/preview/${row.id}?parentId=${row.parentId}`)
    }
  }

  @Provide() openWithLocal (row: any) {
    if (row?.type === 'dir') {

    } else {
      if (!hasPerm('download', row)) {
        this.$message.info('没有本地程序打开权限')
        return
      }
      const handler = this.$message({
        showClose: true,
        duration: 0,
        message: '打开中, 请稍等...'
      })
      download(row.id).then(url => {
        const response = {
          'content-disposition': `attachment; filename=${encodeURIComponent(row.name)}`
        }
        return this.$oss.signatureUrl(url, { response }).then(url => {
          return downloadTo(url, genTmpPath(row.name), true).then(({ path, sha }) => {
            // const { shell } = remote
            // return shell.openPath(data)
            console.log(`打开前文件[${path}]指纹: `, sha)
            const watcher = watchFile(path, () => {
              console.log(`文件[${path}]发生变化`)
              uploadFromLocal(path, row.name, row.parentId).then(() => {
                console.log(`文件[${path}]已同步本地修改到云端`)
                this.$message.success('文件已同步本地修改到云端')
              })
            })
            putInCache(path, () => {
              console.log(`文件[${path}]已退出本地编辑`)
              this.$message.info(`文件${row.name}已退出本地编辑`)
              watcher.close()
            })
            openEx(path, { wait: true }).then(() => {
              // genSha(path).then(newSha => {
              //   console.log(`打开后文件[${path}]指纹: `, newSha)
              //   if (newSha === sha) {
              //     console.log(`文件[${path}]没有发生变化`)
              //     return
              //   }
              //   uploadFromLocal(path, row.name, row.parentId).then(() => {
              //     console.log(`文件[${path}]已同步本地修改到云端`)
              //     this.$message.success('文件已同步本地修改到云端')
              //   })
              // })
            }).finally(() => {
              // console.log(`文件[${path}]已退出本地编辑`)
              // this.$message.info(`文件${row.name}已退出本地编辑`)
              // watcher.close()
              removeFromCache(path)
            })
          })
        })
      }).finally(() => {
        if (handler) handler.close()
      })
    }
  }

  @Provide() onlineEditDoc (row: any) {
    // window.open(this.$router.resolve(`/editor/${row.id}`).href)
    openWindow(`/editor/${row.id}`)
  }

  @Provide() sortDoc (prop: string, order: 'asc' | 'desc') {
    const sorter = { prop, order }
    this.$storage.set('doc_query_sorter', sorter)
    this.pageData.sort(sorter)
  }

  @Provide() filterDoc (name: string, value: any) {
    const params = !name ? {} : { [name]: value }
    this.pageData.filter(params)
  }

  @Provide() listDoc (name: string) {
    this.$storage.set('doc_query_list', name)
    this.listType = name
  }

  @Provide() downloadDoc (row?: any) {
    const items = row ? [row] : this.checkedRows
    // if (items.length > 1 || items[0].type === 'dir') {
    //   const handler = this.$message({
    //     showClose: true,
    //     message: '正在生成压缩包 ... ',
    //     type: 'info',
    //     duration: 0
    //   })
    //   downloadWithPackage(items.map((v: any) => v.id)).then((response: any) => {
    //     const name = decodeURIComponent((response.headers['content-disposition'] || '').replace('attachment;filename=', ''))
    //     downloadFromBlob(response.data, name)
    //   }).finally(() => {
    //     if (handler) handler.close()
    //   })
    //   return
    // }
    // download(items[0].id).then(url => {
    //   const response = {
    //     'content-disposition': `attachment; filename=${encodeURIComponent(items[0].name)}`
    //   }
    //   this.$oss.signatureUrl(url, { response }).then(url => {
    //     downloadFromUrl(url)
    //   })
    // })
    this.$downloader.add(items)
  }

  @Provide() delDoc (row?: any) {
    const items = row ? [row] : this.checkedRows
    const name = items.length > 1 ? `所选${items.length}项` : `【${items[0]?.name}】`
    this.$confirm(`确认删除 ${name}, 删除后可以从回收站找回。`, '移至回收站', {
      confirmButtonText: '删除',
      cancelButtonText: '取消',
      confirmButtonClass: 'el-button--danger',
      type: 'warning'
    }).then(() => {
      const request = items.length > 1 ? delInBatch(items.map((v: any) => v.id)) : del(items[0].id)
      request.then(() => {
        this.pageData.reload()
      })
    }).catch(() => {
      // ignore
    })
  }

  @Provide() mvDoc (row?: any) {
    const items = row ? [row] : this.checkedRows
    if (items.length < 1) {
      this.$message.warning('请选择移动的文档')
      return
    }
    this.docMvModal.open(items).then((data: any) => {
      this.$message.success(`成功移动${data}项到目标文件夹`)
      this.pageData.reload()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() mvDocToTarget (rows: any[], target: any) {
    if (rows.length < 1) {
      this.$message.info('请选择移动对象')
      return
    }
    if (!target) {
      this.$message.info('请选择移动目标文件夹')
      return
    }

    this.$confirm(`确认移动【${rows.length}】项。`, '移动', {
      confirmButtonText: '移动',
      cancelButtonText: '取消',
      confirmButtonClass: 'el-button--warning',
      type: 'warning'
    }).then(() => {
      const req = {
        sources: rows.map(v => v.id),
        target: target.id
      }
      mv(req).then(data => {
        this.$message.success(`成功移动${data}项`)
        this.pageData.reload()
      })
    }).catch(() => {
      // ignore
    })
  }

  @Provide() cpDoc (row?: any) {
    const items = row ? [row] : this.checkedRows
    if (items.length < 1) {
      this.$message.warning('请选择复制的文档')
      return
    }
    this.docMvModal.open(items, 'cp').then((data: any) => {
      this.$message.success(`成功复制${data}项到目标文件夹`)
      this.pageData.reload()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() renameDoc (row: any) {
    this.docRenameModal.open(row).then((data: any) => {
      this.$message.success('重命名成功')
      this.pageData.reload()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() selectRow (row: any) {
    this.selectedRow = row || null
  }

  @Provide() publishDoc (row: any) {
    this.docPublishModal.open(row).then((data: any) => {
      this.$message.success('已提交审核')
      this.pageData.reload()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() updateDocTags (row: any) {
    this.docAddTagModal.open(row).then((data: any) => {
      Object.assign(row, { tags: data || [] })
      this.$message.success('标签已更新')
    }).catch(() => {
      // ignore
    })
  }

  @Provide() removeDocTag (row: any, tag: string) {
    this.$confirm(`确认删除标签【${tag}？`, '删除标签', {
      confirmButtonText: '删 除',
      cancelButtonText: '取 消',
      confirmButtonClass: 'el-button--danger',
      type: 'warning'
    }).then(() => {
      const tags: Set<string> = new Set(row.tags || [])
      tags.delete(tag)
      updateTags(row.id, Array.from(tags)).then((data: any) => {
        Object.assign(row, { tags: data })
        this.$message.success('移除标签成功')
      })
    }).catch(() => {
      // ignore
    })
  }

  @Provide() lockDoc (row: any) {
    this.docLockModal.open(row).then((data: any = {}) => {
      const { lockTime, locked, lockedBy } = data
      Object.assign(row, { lockTime, locked, lockedBy })
      this.$message.success('已锁定')
    }).catch(() => {
      // ignore
    })
  }

  @Provide() unlockDoc (row: any) {
    this.$confirm(`确认解锁文件【${row.name}】？`, '解锁', {
      confirmButtonText: '解 锁',
      cancelButtonText: '取 消',
      // confirmButtonClass: 'el-button--danger',
      type: 'warning'
    }).then(() => {
      unlock(row.id).then((data: any) => {
        Object.assign(row, { lockTime: null, locked: false, lockedBy: null })
        this.$message.success('已解锁')
      })
    }).catch(() => {
      // ignore
    })
  }

  @Provide() createDocLink (row: any) {
    this.docLinkEditModal.open(row).then(() => {

    }).catch(() => {
      // ignore
    })
  }

  @Provide() onAuthorize (row: any) {
    this.docAuthorizeModal.open(row).then(() => {
      this.$message.success('编辑权限成功')
      // this.loadData()
    }).catch(() => {
      // ignore
    })
  }

  @Provide() async uploadConfirm (files: FileList, parentId: number) {
    const rootDir = getRootDir(files)
    if (rootDir) {
      const exists = await existsName(parentId, rootDir)
      if (exists) {
        if (!await this.$confirm('该文件夹已存在，是否要覆盖?', '提示').then(() => true).catch(() => false)) {
          return false
        }
      }
    }
    return true

    function getRootDir (files: FileList) {
      const file: any = files[0]
      const path = file?.webkitRelativePath
      const seg = (path || '').split('/')
      return seg.length > 1 ? seg[0] : ''
    }
  }

  @Provide() applyFlow (row: any) {
    this.docFlowModal.open(row).then((data: any) => {
      this.$message.success('已发起流程')
    }).catch((e: any) => {
      // ignore
    })
  }

  onDragover (e: DragEvent) {
    if (document.body.getAttribute('dragging')) return
    if (!this.hasPerm('upload', this.current)) return
    if (this.uploadZone) this.uploadZone.style.display = 'block'
  }

  onDragLeave (e: Event) {
    if (this.uploadZone) this.uploadZone.style.display = 'none'
  }

  onDrop (e: DragEvent) {
    const items = e.dataTransfer?.items as DataTransferItemList
    // const dir = !files[0]?.webkitGetAsEntry()?.isFile
    // const attrs = ['directory', 'webkitdirectory']
    // if (this.inputFile) {
    //   if (dir) {
    //     attrs.forEach(v => this.inputFile.setAttribute(v, v))
    //   } else {
    //     attrs.forEach(v => this.inputFile.removeAttribute(v))
    //   }
    // }
    e.preventDefault()
    if (this.uploadZone) this.uploadZone.style.display = 'none'

    const all: any[] = []
    for (let i = 0; i < items.length; i++) {
      const item = items[i]?.webkitGetAsEntry()
      // all.push(traverseFile(item as any))
      all.push(item)
    }
    const confirmPm = (v: any) => {
      return this.uploadConfirm([{ webkitRelativePath: v.name + '/_t' }] as any, this.current?.id).then(w => {
        if (w) return v
        return false
      })
    }
    Promise.all(
      all.map(v => v.isFile ? Promise.resolve(v) : confirmPm(v))
    ).then(v => {
      return v.filter(w => !!w)
    }).then(data => {
      if (data.length < 1) return
      Promise.all(data.map(v => traverseFile(v))).then(v => {
        const files: any[] = []
        v.forEach(w => files.push(...w))
        this.uploadFiles(files, this.current, this.load)
      })
    })

    function traverseFile (item: any, path = ''): Promise<any[]> {
      return new Promise((resolve, reject) => {
        if (item.isFile) {
          (item as any).file((file: any) => {
            Object.defineProperty(file, 'webkitRelativePath', {
              get () {
                return path + file.name
              }
            })
            resolve([file])
          })
        } else if (item.isDirectory) {
          (item as any).createReader().readEntries((entries: any) => {
            const all: any[] = []
            for (let i = 0; i < entries.length; i++) {
              all.push(traverseFile(entries[i], path + item.name + '/'))
            }
            Promise.all(all).then(value => {
              const ret: any[] = []
              value.forEach(v => ret.push(...v))
              resolve(ret)
            })
          }, function (e: Error) {
            reject(e)
          })
        }
      })
    }
  }

  onMouseUp () {
    this.$nextTick(() => {
      if (this.uploadZone) this.uploadZone.style.display = 'none'
    })
  }

  async onInputFileChange () {
    try {
      if (await this.uploadConfirm(this.inputFile.files as FileList, this.current?.id)) {
        const files = this.inputFile.files as FileList
        this.uploadFiles(files, this.current, this.load)
      }
    } finally {
      this.inputFile.value = ''
      if (this.uploadZone) this.uploadZone.style.display = 'none'
    }
  }

  onShortcutPaste (text: string) {
    if (this.cpMvPasting) return
    const data = JSON.parse(atob(text))
    const type = data.type
    const sources = data.data || []
    const req = {
      sources: sources,
      target: this.current.id
    }
    this.cpMvPasting = true
    const handler = this.$message({
      message: `${type === 'move' ? '移动' : '复制'}中...`,
      type: 'info',
      duration: 0
    })
    const request = type === 'move' ? mv(req) : cp(req)
    request.then(() => {
      this.pageData.reload(true)
    }).finally(() => {
      handler.close()
      this.cpMvPasting = false
    })
  }

  initShortcutEvent () {
    const container = this.content

    // eslint-disable-next-line no-undef
    const pasteHandler = (e: ClipboardEventInit) => {
      const content = e.clipboardData?.getData('text/plain')
      const flag = 'Rawa_File_CpMv:'
      if (!content || typeof content !== 'string' || !content.startsWith(flag)) return
      this.onShortcutPaste(content.slice(flag.length))
    }
    container.addEventListener('paste', pasteHandler)

    return function destroy () {
      container.removeEventListener('paste', pasteHandler)
    }
  }

  loadData () {
    if (this.id) {
      getDocById(this.id).then(data => {
        this.current = data || null
      })
    } else {
      if (this.entId) {
        getEntRootDoc(this.entId).then(data => {
          this.current = data || null
        })
      } else {
        getUserRootDoc().then(data => {
          this.current = data || null
        })
      }
    }
  }

  @Provide() load () {
    this.loadData()
    if (this.pageData) this.pageData.load()
  }

  @Watch('id', { immediate: true }) idChange () {
    this.load()
    this.selectedRow = null
  }

  mounted () {
    const fn = this.initShortcutEvent()
    this.$once('hook:beforeDestroy', function () {
      fn()
    })
  }

  created () {
    const loader = (params: PageQueryParams) => {
      let p
      let sorter = this.$storage.get('doc_query_sorter') as any
      if (!sorter) {
        sorter = { prop: 'modifiedTime', order: 'desc' }
        this.$storage.set('doc_query_sorter', sorter)
      }
      const extra = Object.assign(sorter, params)
      if (this.id) p = getUserDocs(extra, this.id)
      else {
        if (this.entId) {
          p = getEntRootDocs(this.entId, extra)
        } else {
          p = getUserRootDocs(extra)
        }
      }
      return p.then(data => {
        data.content.forEach(w => {
          w.checked = false
          if (this.selectedId && !this.selectedRow && Number(this.selectedId) === w.id) {
            this.selectedRow = w
          }
        })
        return data
      })
    }
    this.pageData = new PageData(loader, { size: 50 })

    if (!this.entId && this.currentSpace?.type !== 'user') {
      const space = this.spaces.find((v: any) => v.type === 'user')
      this.setCurrentSpace(space)
    }
    if (this.entId && this.currentSpace?.ownId !== Number(this.entId)) {
      const space = this.spaces.find((v: any) => v.ownId === Number(this.entId))
      if (space) this.setCurrentSpace(space)
      else {
        this.$message.error('找不到该企业')
        this.$router.push('/docs')
      }
    }
  }
}
</script>

<style lang="scss" module>
@import "~@/styles/vars.scss";

$header-height: 40px;

.container {
  height: 100%;
}

.header {
  height: $header-height;
  background-color: $bg-color;
  border-bottom: 1px solid $border-split-color;
  box-sizing: border-box;
}

.body {
  display: flex;
  height: calc(100% - #{$header-height});
}

.menu {
  width: 160px;
  flex: 0 0 auto;
  background: chocolate;
  height: 100%;
  overflow: auto;
}

.content {
  flex: 1 1 auto;
  height: 100%;
  background-color: $bg-color;
  position: relative;
}

.contentInner {
  height: 100%;
  overflow: auto;
}

.aside {
  width: 300px;
  flex: 0 0 auto;
  height: 100%;
  overflow: auto;
}

.uploadZone {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(100, 100, 100, 0.1);
  border: 1px dashed $color-primary;
  display: none;

  &::after {
    content: "拖入到当前文件夹上传";
    position: absolute;
    color: #fff;
    z-index: 0;
    margin-top: 20%;
    font-size: 24px;
    width: 100%;
    text-align: center;
  }
}

.inputFile {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  z-index: 2;
  opacity: 0;
}
</style>
